// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Crate for displaying simple surfaces and GPU buffers over a low-level display backend such as
//! Wayland or X.

use std::collections::BTreeMap;
use std::fmt::{self, Display};
use std::io::Error as IoError;
use std::path::Path;
use std::time::Duration;

use base::{AsRawDescriptor, Error as BaseError, EventType, PollToken, RawDescriptor, WaitContext};
use data_model::VolatileSlice;

mod event_device;
mod gpu_display_stub;
mod gpu_display_wl;
#[cfg(feature = "x")]
mod gpu_display_x;
#[cfg(feature = "x")]
mod keycode_converter;

pub use event_device::{EventDevice, EventDeviceKind};
use linux_input_sys::virtio_input_event;

/// An error generated by `GpuDisplay`.
#[derive(Debug)]
pub enum GpuDisplayError {
    /// An internal allocation failed.
    Allocate,
    /// Connecting to the compositor failed.
    Connect,
    /// Creating event file descriptor failed.
    CreateEvent,
    /// A base error occurred.
    BaseError(BaseError),
    /// Failed to create a surface on the compositor.
    CreateSurface,
    /// Failed to import a buffer to the compositor.
    FailedImport,
    /// The path is invalid.
    InvalidPath,
    /// The import ID is invalid.
    InvalidImportId,
    /// The surface ID is invalid.
    InvalidSurfaceId,
    /// An input/output error occured.
    IoError(IoError),
    /// A required feature was missing.
    RequiredFeature(&'static str),
    /// The method is unsupported by the implementation.
    Unsupported,
}

pub type GpuDisplayResult<T> = std::result::Result<T, GpuDisplayError>;

impl Display for GpuDisplayError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::GpuDisplayError::*;

        match self {
            Allocate => write!(f, "internal allocation failed"),
            Connect => write!(f, "failed to connect to compositor"),
            CreateEvent => write!(f, "failed to create event file descriptor"),
            BaseError(e) => write!(f, "received a base error: {}", e),
            CreateSurface => write!(f, "failed to crate surface on the compositor"),
            FailedImport => write!(f, "failed to import a buffer to the compositor"),
            InvalidPath => write!(f, "invalid path"),
            InvalidImportId => write!(f, "invalid import ID"),
            InvalidSurfaceId => write!(f, "invalid surface ID"),
            IoError(e) => write!(f, "an input/output error occur: {}", e),
            RequiredFeature(feature) => write!(f, "required feature was missing: {}", feature),
            Unsupported => write!(f, "unsupported by the implementation"),
        }
    }
}

impl From<BaseError> for GpuDisplayError {
    fn from(e: BaseError) -> GpuDisplayError {
        GpuDisplayError::BaseError(e)
    }
}

impl From<IoError> for GpuDisplayError {
    fn from(e: IoError) -> GpuDisplayError {
        GpuDisplayError::IoError(e)
    }
}

/// A surface type
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SurfaceType {
    /// Scanout surface
    Scanout,
    /// Mouse cursor surface
    Cursor,
}

/// Poll token for display instances
#[derive(PollToken)]
pub enum DisplayPollToken {
    Display,
    EventDevice { event_device_id: u32 },
}

#[derive(Clone)]
pub struct GpuDisplayFramebuffer<'a> {
    framebuffer: VolatileSlice<'a>,
    slice: VolatileSlice<'a>,
    stride: u32,
    bytes_per_pixel: u32,
}

impl<'a> GpuDisplayFramebuffer<'a> {
    fn new(
        framebuffer: VolatileSlice<'a>,
        stride: u32,
        bytes_per_pixel: u32,
    ) -> GpuDisplayFramebuffer {
        GpuDisplayFramebuffer {
            framebuffer,
            slice: framebuffer,
            stride,
            bytes_per_pixel,
        }
    }

    fn sub_region(
        &self,
        x: u32,
        y: u32,
        width: u32,
        height: u32,
    ) -> Option<GpuDisplayFramebuffer<'a>> {
        let x_byte_offset = x.checked_mul(self.bytes_per_pixel)?;
        let y_byte_offset = y.checked_mul(self.stride)?;
        let byte_offset = x_byte_offset.checked_add(y_byte_offset)?;

        let width_bytes = width.checked_mul(self.bytes_per_pixel)?;
        let count = height
            .checked_mul(self.stride)?
            .checked_sub(self.stride)?
            .checked_add(width_bytes)?;
        let slice = self
            .framebuffer
            .sub_slice(byte_offset as usize, count as usize)
            .unwrap();

        Some(GpuDisplayFramebuffer { slice, ..*self })
    }

    pub fn as_volatile_slice(&self) -> VolatileSlice<'a> {
        self.slice
    }

    pub fn stride(&self) -> u32 {
        self.stride
    }
}

/// Empty trait, just used as a bounds for now
trait GpuDisplayImport {}

trait GpuDisplaySurface {
    /// Returns an unique ID associated with the surface.  This is typically generated by the
    /// compositor or cast of a raw pointer.
    fn surface_descriptor(&self) -> u64 {
        0
    }

    /// Returns the next framebuffer, allocating if necessary.
    fn framebuffer(&mut self) -> Option<GpuDisplayFramebuffer>;

    /// Returns true if the next buffer in the swapchain is already in use.
    fn next_buffer_in_use(&self) -> bool {
        false
    }

    /// Returns true if the surface should be closed.
    fn close_requested(&self) -> bool {
        false
    }

    /// Puts the next buffer on the screen, making it the current buffer.
    fn flip(&mut self) {
        // no-op
    }

    /// Puts the specified import_id on the screen.
    fn flip_to(&mut self, _import_id: u32) {
        // no-op
    }

    /// Commits the surface to the compositor.
    fn commit(&mut self) -> GpuDisplayResult<()> {
        Ok(())
    }

    /// Sets the position of the identified subsurface relative to its parent.
    fn set_position(&mut self, _x: u32, _y: u32) {
        // no-op
    }

    /// Returns the type of the completed buffer.
    fn buffer_completion_type(&self) -> u32 {
        0
    }

    /// Draws the current buffer on the screen.
    fn draw_current_buffer(&mut self) {
        // no-op
    }

    /// Handles a compositor-specific client event.
    fn on_client_message(&mut self, _client_data: u64) {
        // no-op
    }

    /// Handles a compositor-specific shared memory completion event.
    fn on_shm_completion(&mut self, _shm_complete: u64) {
        // no-op
    }

    /// Sets the scanout ID for the surface.
    fn set_scanout_id(&mut self, _scanout_id: u32) {
        // no-op
    }
}

struct GpuDisplayEvents {
    events: Vec<virtio_input_event>,
    device_type: EventDeviceKind,
}

trait DisplayT: AsRawDescriptor {
    /// Returns true if there are events that are on the queue.
    fn pending_events(&self) -> bool {
        false
    }

    /// Sends any pending commands to the compositor.
    fn flush(&self) {
        // no-op
    }

    /// Returns the surface descirptor associated with the current event
    fn next_event(&mut self) -> GpuDisplayResult<u64> {
        Ok(0)
    }

    /// Handles the event from the compositor, and returns an list of events
    fn handle_next_event(
        &mut self,
        _surface: &mut Box<dyn GpuDisplaySurface>,
    ) -> Option<GpuDisplayEvents> {
        None
    }

    /// Creates a surface with the given parameters.  The display backend is given a non-zero
    /// `surface_id` as a handle for subsequent operations.
    fn create_surface(
        &mut self,
        parent_surface_id: Option<u32>,
        surface_id: u32,
        width: u32,
        height: u32,
        surf_type: SurfaceType,
    ) -> GpuDisplayResult<Box<dyn GpuDisplaySurface>>;

    /// Imports memory into the display backend.  The display backend is given a non-zero
    /// `import_id` as a handle for subsequent operations.
    fn import_memory(
        &mut self,
        _import_id: u32,
        _descriptor: &dyn AsRawDescriptor,
        _offset: u32,
        _stride: u32,
        _modifiers: u64,
        _width: u32,
        _height: u32,
        _fourcc: u32,
    ) -> GpuDisplayResult<Box<dyn GpuDisplayImport>> {
        Err(GpuDisplayError::Unsupported)
    }
}

/// A connection to the compositor and associated collection of state.
///
/// The user of `GpuDisplay` can use `AsRawDescriptor` to poll on the compositor connection's file
/// descriptor. When the connection is readable, `dispatch_events` can be called to process it.
pub struct GpuDisplay {
    next_id: u32,
    event_devices: BTreeMap<u32, EventDevice>,
    surfaces: BTreeMap<u32, Box<dyn GpuDisplaySurface>>,
    imports: BTreeMap<u32, Box<dyn GpuDisplayImport>>,
    // `inner` must be after `imports` and `surfaces` to ensure those objects are dropped before
    // the display context. The drop order for fields inside a struct is the order in which they
    // are declared [Rust RFC 1857].
    inner: Box<dyn DisplayT>,
    wait_ctx: WaitContext<DisplayPollToken>,
    is_x: bool,
}

impl GpuDisplay {
    /// Opens a connection to X server
    pub fn open_x<S: AsRef<str>>(display_name: Option<S>) -> GpuDisplayResult<GpuDisplay> {
        let _ = display_name;
        #[cfg(feature = "x")]
        {
            let display = match display_name {
                Some(s) => gpu_display_x::DisplayX::open_display(Some(s.as_ref()))?,
                None => gpu_display_x::DisplayX::open_display(None)?,
            };

            let wait_ctx = WaitContext::new()?;
            wait_ctx.add(&display, DisplayPollToken::Display)?;

            Ok(GpuDisplay {
                inner: Box::new(display),
                next_id: 1,
                event_devices: Default::default(),
                surfaces: Default::default(),
                imports: Default::default(),
                wait_ctx,
                is_x: true,
            })
        }
        #[cfg(not(feature = "x"))]
        Err(GpuDisplayError::Unsupported)
    }

    /// Opens a fresh connection to the compositor.
    pub fn open_wayland<P: AsRef<Path>>(wayland_path: Option<P>) -> GpuDisplayResult<GpuDisplay> {
        let display = match wayland_path {
            Some(s) => gpu_display_wl::DisplayWl::new(Some(s.as_ref()))?,
            None => gpu_display_wl::DisplayWl::new(None)?,
        };

        let wait_ctx = WaitContext::new()?;
        wait_ctx.add(&display, DisplayPollToken::Display)?;

        Ok(GpuDisplay {
            inner: Box::new(display),
            next_id: 1,
            event_devices: Default::default(),
            surfaces: Default::default(),
            imports: Default::default(),
            wait_ctx,
            is_x: false,
        })
    }

    pub fn open_stub() -> GpuDisplayResult<GpuDisplay> {
        let display = gpu_display_stub::DisplayStub::new()?;
        let wait_ctx = WaitContext::new()?;
        wait_ctx.add(&display, DisplayPollToken::Display)?;

        Ok(GpuDisplay {
            inner: Box::new(display),
            next_id: 1,
            event_devices: Default::default(),
            surfaces: Default::default(),
            imports: Default::default(),
            wait_ctx,
            is_x: false,
        })
    }

    /// Return whether this display is an X display
    pub fn is_x(&self) -> bool {
        self.is_x
    }

    fn handle_event_device(&mut self, event_device_id: u32) {
        if let Some(event_device) = self.event_devices.get(&event_device_id) {
            // TODO(zachr): decode the event and forward to the device.
            let _ = event_device.recv_event_encoded();
        }
    }

    fn dispatch_display_events(&mut self) -> GpuDisplayResult<()> {
        self.inner.flush();
        while self.inner.pending_events() {
            let surface_descriptor = self.inner.next_event()?;

            for surface in self.surfaces.values_mut() {
                if surface_descriptor != surface.surface_descriptor() {
                    continue;
                }

                if let Some(gpu_display_events) = self.inner.handle_next_event(surface) {
                    for event_device in self.event_devices.values_mut() {
                        if event_device.kind() != gpu_display_events.device_type {
                            continue;
                        }

                        event_device.send_report(gpu_display_events.events.iter().cloned())?;
                    }
                }
            }
        }

        Ok(())
    }

    /// Dispatches internal events that were received from the compositor since the last call to
    /// `dispatch_events`.
    pub fn dispatch_events(&mut self) -> GpuDisplayResult<()> {
        let wait_events = self.wait_ctx.wait_timeout(Duration::default())?;
        for wait_event in wait_events.iter().filter(|e| e.is_writable) {
            if let DisplayPollToken::EventDevice { event_device_id } = wait_event.token {
                if let Some(event_device) = self.event_devices.get_mut(&event_device_id) {
                    if !event_device.flush_buffered_events()? {
                        continue;
                    }
                    self.wait_ctx.modify(
                        event_device,
                        EventType::Read,
                        DisplayPollToken::EventDevice { event_device_id },
                    )?;
                }
            }
        }

        for wait_event in wait_events.iter().filter(|e| e.is_readable) {
            match wait_event.token {
                DisplayPollToken::Display => self.dispatch_display_events()?,
                DisplayPollToken::EventDevice { event_device_id } => {
                    self.handle_event_device(event_device_id)
                }
            }
        }

        Ok(())
    }

    /// Creates a surface on the the compositor as either a top level window, or child of another
    /// surface, returning a handle to the new surface.
    pub fn create_surface(
        &mut self,
        parent_surface_id: Option<u32>,
        width: u32,
        height: u32,
        surf_type: SurfaceType,
    ) -> GpuDisplayResult<u32> {
        if let Some(parent_id) = parent_surface_id {
            if !self.surfaces.contains_key(&parent_id) {
                return Err(GpuDisplayError::InvalidSurfaceId);
            }
        }

        let new_surface_id = self.next_id;
        let new_surface = self.inner.create_surface(
            parent_surface_id,
            new_surface_id,
            width,
            height,
            surf_type,
        )?;

        self.next_id += 1;
        self.surfaces.insert(new_surface_id, new_surface);
        Ok(new_surface_id)
    }

    /// Releases a previously created surface identified by the given handle.
    pub fn release_surface(&mut self, surface_id: u32) {
        self.surfaces.remove(&surface_id);
    }

    /// Gets a reference to an unused framebuffer for the identified surface.
    pub fn framebuffer(&mut self, surface_id: u32) -> Option<GpuDisplayFramebuffer> {
        let surface = self.surfaces.get_mut(&surface_id)?;
        surface.framebuffer()
    }

    /// Gets a reference to an unused framebuffer for the identified surface.
    pub fn framebuffer_region(
        &mut self,
        surface_id: u32,
        x: u32,
        y: u32,
        width: u32,
        height: u32,
    ) -> Option<GpuDisplayFramebuffer> {
        let framebuffer = self.framebuffer(surface_id)?;
        framebuffer.sub_region(x, y, width, height)
    }

    /// Returns true if the next buffer in the buffer queue for the given surface is currently in
    /// use.
    ///
    /// If the next buffer is in use, the memory returned from `framebuffer_memory` should not be
    /// written to.
    pub fn next_buffer_in_use(&self, surface_id: u32) -> bool {
        self.surfaces
            .get(&surface_id)
            .map(|s| s.next_buffer_in_use())
            .unwrap_or(false)
    }

    /// Changes the visible contents of the identified surface to the contents of the framebuffer
    /// last returned by `framebuffer_memory` for this surface.
    pub fn flip(&mut self, surface_id: u32) {
        if let Some(surface) = self.surfaces.get_mut(&surface_id) {
            surface.flip()
        }
    }

    /// Returns true if the identified top level surface has been told to close by the compositor,
    /// and by extension the user.
    pub fn close_requested(&self, surface_id: u32) -> bool {
        self.surfaces
            .get(&surface_id)
            .map(|s| s.close_requested())
            .unwrap_or(true)
    }

    /// Imports the given `event_device` into the display, returning an event device id on success.
    /// This device may be used to poll for input events.
    pub fn import_event_device(&mut self, event_device: EventDevice) -> GpuDisplayResult<u32> {
        let new_event_device_id = self.next_id;

        self.wait_ctx.add(
            &event_device,
            DisplayPollToken::EventDevice {
                event_device_id: new_event_device_id,
            },
        )?;

        self.event_devices.insert(new_event_device_id, event_device);
        self.next_id += 1;
        Ok(new_event_device_id)
    }

    /// Release an event device from the display, given an `event_device_id`.
    pub fn release_event_device(&mut self, event_device_id: u32) {
        self.event_devices.remove(&event_device_id);
    }

    /// Imports memory to the compositor for use as a surface buffer and returns a handle
    /// to it.
    pub fn import_memory(
        &mut self,
        descriptor: &dyn AsRawDescriptor,
        offset: u32,
        stride: u32,
        modifiers: u64,
        width: u32,
        height: u32,
        fourcc: u32,
    ) -> GpuDisplayResult<u32> {
        let import_id = self.next_id;

        let gpu_display_memory = self.inner.import_memory(
            import_id, descriptor, offset, stride, modifiers, width, height, fourcc,
        )?;

        self.next_id += 1;
        self.imports.insert(import_id, gpu_display_memory);
        Ok(import_id)
    }

    /// Releases a previously imported memory identified by the given handle.
    pub fn release_import(&mut self, import_id: u32) {
        self.imports.remove(&import_id);
    }

    /// Commits any pending state for the identified surface.
    pub fn commit(&mut self, surface_id: u32) -> GpuDisplayResult<()> {
        let surface = self
            .surfaces
            .get_mut(&surface_id)
            .ok_or(GpuDisplayError::InvalidSurfaceId)?;

        surface.commit()
    }

    /// Changes the visible contents of the identified surface to that of the identified imported
    /// buffer.
    pub fn flip_to(&mut self, surface_id: u32, import_id: u32) -> GpuDisplayResult<()> {
        let surface = self
            .surfaces
            .get_mut(&surface_id)
            .ok_or(GpuDisplayError::InvalidSurfaceId)?;

        if !self.imports.contains_key(&import_id) {
            return Err(GpuDisplayError::InvalidImportId);
        }

        surface.flip_to(import_id);
        Ok(())
    }

    /// Sets the position of the identified subsurface relative to its parent.
    ///
    /// The change in position will not be visible until `commit` is called for the parent surface.
    pub fn set_position(&mut self, surface_id: u32, x: u32, y: u32) -> GpuDisplayResult<()> {
        let surface = self
            .surfaces
            .get_mut(&surface_id)
            .ok_or(GpuDisplayError::InvalidSurfaceId)?;

        surface.set_position(x, y);
        Ok(())
    }

    /// Associates the scanout id with the given surface.
    pub fn set_scanout_id(&mut self, surface_id: u32, scanout_id: u32) -> GpuDisplayResult<()> {
        let surface = self
            .surfaces
            .get_mut(&surface_id)
            .ok_or(GpuDisplayError::InvalidSurfaceId)?;

        surface.set_scanout_id(scanout_id);
        Ok(())
    }
}

impl AsRawDescriptor for GpuDisplay {
    fn as_raw_descriptor(&self) -> RawDescriptor {
        self.wait_ctx.as_raw_descriptor()
    }
}
